package com.m1905.lib.download.request.http;

import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_PRECON_FAILED;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;

import android.os.Build;

import com.m1905.lib.download.request.BaseDownloadRequest;
import com.m1905.lib.download.request.StopException;
import com.m1905.lib.download.util.DLog;
import com.m1905.lib.download.util.IoUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;

/**
 * @author wanghao
 * @date 2022/8/2.
 * @des
 */
public class HttpUrlConnectionRequest extends BaseDownloadRequest {

    public void startDownload() {
        DLog.e("HttpUrlConnectionRequest download urlStr: " + urlStr + " , filePath:" + mFilePath + " , isResume:" + isResume);
        URL url = null;
        int redirectionCount = 0;

        try {
            url = new URL(urlStr);
        } catch (MalformedURLException e) {
            e.printStackTrace();
            throw new StopException(STATUS_URL_ERROR, e.getMessage());
        }

        File file;
        try {
            if (mFilePath == null) {
                throw new StopException(STATUS_FILE_ERROR, "file path is null");
            }
            file = new File(mFilePath);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
        } catch (Exception e) {
            throw new StopException(STATUS_FILE_ERROR, e.getMessage());
        }

        while (redirectionCount++ < MAX_REDIRECTS) {
            DLog.e("HttpUrlConnectionRequest startDownload redirectionCount: " + redirectionCount);
            HttpURLConnection connection = null;
            try {
                connection = (HttpURLConnection) url.openConnection();
                connection.setInstanceFollowRedirects(false);
                connection.setRequestProperty("Accept-Encoding", "identity");
                connection.setRequestProperty("Connection", "close");
                boolean resuming = isResume && file.exists() && file.length() > 0;
                DLog.e("HttpUrlConnectionRequest startDownload resuming: " + resuming + "  fileSize:" + file.length());
                if (resuming) {
                    connection.addRequestProperty("Range", "bytes=" + file.length() + "-");
                    mCurrentBytes = file.length();
                } else {
                    if (file.exists()) {
                        file.delete();
                    }
                }

                connection.setConnectTimeout(DEFAULT_TIMEOUT);
                connection.setReadTimeout(DEFAULT_TIMEOUT);
                int responseCode = 0;
                try {
                    responseCode = connection.getResponseCode();
                } catch (IOException e) {
                    StopException.handlerIOException(e);
                }
                DLog.e("HttpUrlConnectionRequest startDownload responseCode: " + responseCode);
                switch (responseCode) {
                    case HttpURLConnection.HTTP_OK:
                        if (resuming) {
                            throw new StopException(STATUS_CANNOT_RESUME, "Expected partial, but received OK");
                        }
                        transferData(connection, file);
                        return;
                    case HttpURLConnection.HTTP_PARTIAL:// 206 断点续传
                        if (!resuming) {
                            throw new StopException(STATUS_CANNOT_RESUME, "Expected OK, but received partial");
                        }
                        transferData(connection, file);
                        return;
                    case HttpURLConnection.HTTP_MOVED_PERM://301
                    case HttpURLConnection.HTTP_MOVED_TEMP://302
                    case HttpURLConnection.HTTP_SEE_OTHER://303
                    case HTTP_TEMP_REDIRECT://307
                        // 重定向
                        String location = connection.getHeaderField("Location");
                        url = new URL(url, location);
                        DLog.e("HttpUrlConnectionRequest download redirect to : " + redirectionCount + "  " + location);
                        continue;
                    case HTTP_PRECON_FAILED:// 412
                        throw new StopException(STATUS_CANNOT_RESUME, "Precondition failed");
//                    break;
                    case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE://416
                        throw new StopException(STATUS_CANNOT_RESUME, "Requested range not satisfiable");
                    case HTTP_UNAVAILABLE://503
                        throw new StopException(HTTP_UNAVAILABLE, connection.getResponseMessage());
                    case HTTP_INTERNAL_ERROR://500
                        throw new StopException(HTTP_INTERNAL_ERROR, connection.getResponseMessage());
                    default:
                        throw StopException.throwUnhandledHttpError(responseCode, connection.getResponseMessage());
                }

            } catch (IOException e) {
                e.printStackTrace();
                DLog.e("HttpUrlConnectionRequest IOException : " + e.getMessage());
                throw new StopException(e.getMessage());
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }

        throw new StopException(STATUS_TOO_MANY_REDIRECTS, "Too many redirects");

    }


    /**
     * Transfer data from the given connection to the destination file.
     */
    private void transferData(HttpURLConnection conn, File file) throws StopException {

        // To detect when we're really finished, we either need a length, closed
        // connection, or chunked encoding.
//        final boolean hasLength = mDownloadInfo.mTotalBytes != -1;
        final boolean isConnectionClose = "close".equalsIgnoreCase(conn.getHeaderField("Connection"));
        final boolean isEncodingChunked = "chunked".equalsIgnoreCase(conn.getHeaderField("Transfer-Encoding"));

        final boolean finishKnown = isConnectionClose || isEncodingChunked;
        if (!finishKnown) {
            throw new StopException(STATUS_CANNOT_RESUME, "can't know size of download, giving up");
        }
        final String transferEncoding = conn.getHeaderField("Transfer-Encoding");
        if (transferEncoding == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                mTotalBytes = getHeaderFieldLong(conn, "Content-Length", -1);
                if (mTotalBytes > 0) {
                    mTotalBytes += mCurrentBytes;
                }
            }
        } else {
            mTotalBytes = -1;
        }

        if (mTotalBytes == -1) {
            throw new StopException(STATUS_CANNOT_RESUME, "can't know size of download, giving up");
        }

        if (mTotalBytes == 0) {
            throw new StopException(STATUS_CANNOT_RESUME, "file size is 0, giving up");
        }

        checkFileLength(file, mTotalBytes);
        if (mListener != null) {
            mListener.onFileSize(mTotalBytes);
        }
        updateProgress(false);
        InputStream in = null;
        OutputStream out = null;
        try {
            try {
                in = conn.getInputStream();
            } catch (IOException e) {
                StopException.handlerIOException(e);
            }
            // Start streaming data, periodically watch for pause/cancel
            // commands and checking disk space as needed.

            try {
                out = new FileOutputStream(file, true);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                throw new StopException(STATUS_FILE_ERROR, e);
            }

            final byte[] buffer = new byte[BUFFER_SIZE];
            while (true) {
                checkInterrupted();
                int len = -1;
                try {
                    len = in.read(buffer);
                } catch (IOException e) {
                    if (e instanceof UnknownHostException || e instanceof SocketException) {
                        throw new StopException(STATUS_NETWORK_ERROR, e.getMessage());
                    } else if (e instanceof SocketTimeoutException) {
                        throw new StopException(STATUS_TIMEOUT_ERROR, e.getMessage());
                    } else {
                        throw new StopException(STATUS_HTTP_DATA_ERROR, "Failed reading response: " + e.getMessage());
                    }
                }

                if (len == -1) {//下载完成，结束循环
                    break;
                }

                try {
                    // When streaming, ensure space before each write
                    checkAvailableSpace();
                    out.write(buffer, 0, len);
                    mCurrentBytes += len;
                    updateProgress(true);
                } catch (Exception e) {
                    throw new StopException(STATUS_FILE_ERROR, e);
                }
            }

            // Finished without error; verify length if known
            if (mTotalBytes != -1 && mCurrentBytes != mTotalBytes) {
                throw new StopException(STATUS_HTTP_DATA_ERROR, "Content length mismatch");
            }
            updateProgress(false);
            DLog.e("HttpUrlConnectionRequest  downloadCompleted : ");
            if (mListener != null) {
                mListener.onCompleted();
            }
        } finally {
            IoUtils.closeQuietly(in);
            try {
                if (out != null) out.flush();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                IoUtils.closeQuietly(out);
            }
        }
    }


    private static long getHeaderFieldLong(URLConnection conn, String field, long defaultValue) {
        try {
            return Long.parseLong(conn.getHeaderField(field));
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    private void checkInterrupted() {
        if (Thread.interrupted()) {
            DLog.e("HttpUrlConnectionRequest checkInterrupted true");
            throw new StopException("isAborted request, give up download");
        }
    }

    private long mLastUpdateProgressTimeMillis;
    private long mLastUpdateProgressBytes;

    private void updateProgress(boolean isDelay) {
        long current = System.currentTimeMillis();
        long mUpdateTime = current - mLastUpdateProgressTimeMillis;
        if (!isDelay || mUpdateTime > 1000) {
            long mLastUpdateProgressSpeed = mLastUpdateProgressTimeMillis > 0 && mLastUpdateProgressBytes > 0 ? (mCurrentBytes - mLastUpdateProgressBytes) / mUpdateTime : 0;
            mLastUpdateProgressTimeMillis = current;
            mLastUpdateProgressBytes = mCurrentBytes;
            DLog.e("HttpUrlConnectionRequest download progress mTotalBytes: " + mTotalBytes + "  , mCurrentBytes:" + mCurrentBytes + "  " + (mCurrentBytes * 100 / mTotalBytes) + "%  takeTime:" + (mUpdateTime + " speed:  " + mLastUpdateProgressSpeed));
            if (mListener != null) {
                mListener.onProgress(mTotalBytes, mCurrentBytes, mLastUpdateProgressSpeed);
            }
        }
    }


    private void checkAvailableSpace() {
        //TODO 写文件之前检查存储卡可用空间

    }

    //对比是否和上次下载时文件长度相等，不相等说明流已变化，放弃下载
    private void checkFileLength(File file, long total) {
        if (file.length() > 0 && mLastTotalSize > 0 && mLastTotalSize != total) {
            throw new StopException(STATUS_CANNOT_RESUME, "file length changed, give up request mLastTotalSize:" + mLastTotalSize + "  currentTotal:" + total);
        }
    }

}
